using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Net;
using System.IO;
using Library.Logging;

namespace Library.HttpTools
{
    public class MiniBrowser
    {
        private ILog log;
        private Dictionary<string, string> input = new Dictionary<string, string>();
        private Dictionary<string, string> backupInput = new Dictionary<string, string>();
        private string content = string.Empty;
        private byte[] binaryContent = new byte[] { };
        private string url = string.Empty;
        private string lastSuccessfulUrl = string.Empty;
        private bool lastMethodWasPost = false;
        private WebExceptionStatus lastExceptionStatus = WebExceptionStatus.UnknownError;
        private string lastExceptionMessage = string.Empty;
        private string lastExceptionStackTrace = string.Empty;
        private int timeoutSecs = 15;
        private int redirectCount = 0;
        private int maxRedirectCount = 10;
        private long contentLength = 0;
        private bool lastWasError = false;
        private string fileName = string.Empty;
        private bool isAttachedFile = false;
        private CookieCollection cookies = null;
        private Stream responseCopyStream;
        private ICredentials _credentials;

        public MiniBrowser(ILog ilog)
        {
            log = ilog;
        }

        public ICredentials Credentials
        {
            get { return _credentials; }
            set { _credentials = value; }
        }

        public int RedirectCount
        {
            get { return redirectCount; }
            set { redirectCount = value; }
        }

        public int MaxRedirectCount
        {
            get { return maxRedirectCount; }
            set { maxRedirectCount = value; }
        }

        public bool LastWasError
        {
            get { return lastWasError; }
        }

        public ILog Log
        {
            get { return log; }
            set { log = value; }
        }

        public bool IsAttachedFile
        {
            get { return isAttachedFile; }
        }

        public string AttachedFileName
        {
            get { return fileName; }
        }

        public Stream RedirectOutputStream
        {
            get { return responseCopyStream; }
            set { responseCopyStream = value; }
        }

        public string FixUrl(string relurl)
        {
            if (relurl.StartsWith("http:", StringComparison.CurrentCultureIgnoreCase) ||
                relurl.StartsWith("https:", StringComparison.CurrentCultureIgnoreCase))
            {
                if (relurl.Length < 10) return relurl;
                else return relurl;
            }

            Uri newuri;
            if (string.IsNullOrEmpty(this.url))
            {
                newuri = new Uri(relurl);
            }
            else
            {
                Uri baseuri = new Uri(this.url);
                newuri = new Uri(baseuri, relurl);
            }
            return newuri.ToString();
        }

        public Dictionary<string, string> Input
        {
            get
            {
                return input;
            }
        }

        public bool HttpHead(string url)
        {
            this.redirectCount = 0;
            bool success = httpHead(url);
            if (success) this.clearInput();

            return success;
        }

        public bool HttpGet(string url)
        {
            this.redirectCount = 0;
            bool success = httpGet(url);
            if (success) this.clearInput();

            return success;
        }

        public bool HttpPost(string url)
        {
            this.redirectCount = 0;
            bool success = httpPost(url);
            if (success) this.clearInput();

            return success;
        }

        public bool Refresh()
        {
            bool retval = false;

            this.restoreInput();

            if (lastMethodWasPost)
            {
                retval = this.HttpPost(this.url);
            }
            else
            {
                retval = this.HttpGet(this.url);
            }

            return retval;
        }

        public void Reset()
        {
            this.lastSuccessfulUrl = this.lastExceptionMessage = this.url = string.Empty;
            this.lastExceptionStatus = WebExceptionStatus.UnknownError;
            this.input.Clear();
            this.backupInput.Clear();
        }

        public string URL
        {
            get
            {
                return url;
            }
        }

        public string ReferringURL
        {
            get
            {
                return lastSuccessfulUrl;
            }
        }

        public bool IsBinaryContent
        {
            get
            {
                return (binaryContent.Length > 0);
            }
        }

        public string Content
        {
            get
            {
                return content;
            }
        }

        public byte[] BinaryContent
        {
            get
            {
                return binaryContent;
            }
        }

        public CookieCollection CookieCollectionNative
        {
            get
            {
                return cookies;
            }
        }

        public int Timeout
        {
            get
            {
                return timeoutSecs;
            }
            set
            {
                timeoutSecs = value;
            }
        }

        public long ContentLength
        {
            get
            {
                return contentLength;
            }
        }

        public WebExceptionStatus LastExceptionStatus
        {
            get
            {
                return lastExceptionStatus;
            }
        }

        public string LastExceptionMessage
        {
            get
            {
                return lastExceptionMessage;
            }
        }

        public string LastExceptionStackTrace
        {
            get
            {
                return lastExceptionStackTrace;
            }
        }

        private Uri fixUrl(string urlToFix)
        {
            Uri uri = null;
            if (urlToFix.StartsWith("http://") || urlToFix.StartsWith("https://"))
            {
                uri = new Uri(urlToFix);
            }
            else
            {
                uri = new Uri(new Uri(this.url), urlToFix);
            }
            return uri;
        }

        private bool httpHead(string urlarg)
        {
            Uri uri = this.fixUrl(urlarg);
            string url = uri.ToString();

            this.lastMethodWasPost = false;
            this.url = url;

            bool returnValue = false;
            string setCookie = string.Empty, location = string.Empty;
            HttpStatusCode statusCode = HttpStatusCode.BadRequest;

            //First, build the URL from the get variables
            HttpWebResponse response = null;
            try
            {
                response = HttpHelper.GetHttpResponse("HEAD", url, lastSuccessfulUrl, input, cookies, _credentials, timeoutSecs);

                statusCode = response.StatusCode;
                location = response.Headers["Location"];
                contentLength = response.ContentLength;

                cookies = response.Cookies;

                if (location == null)
                {
                    returnValue = true;
                    this.binaryContent = new byte[] { };
                    this.content = string.Empty;

                    response.Close();
                    response = null;
                }
                else
                {
                    response.Close();
                    response = null;

                    if (redirectCount < maxRedirectCount)
                    {
                        this.input.Clear();
                        // need to forward onto next page
                        returnValue = this.httpHead(location);
                        redirectCount++;
                    }
                }

                this.url = url;
                this.lastSuccessfulUrl = url;
                this.lastWasError = false;
            }
            catch (WebException wex)
            {
                this.lastExceptionStatus = wex.Status;
                this.lastExceptionMessage = wex.Message;
                this.lastExceptionStackTrace = wex.StackTrace;
                this.lastWasError = true;

                this.log.WriteLine("Error Heading [{0}]", url);
                this.log.WriteLine(wex);
                // need to store exception info in browser!
                returnValue = false;
            }

            return returnValue;
        }

        private bool httpGet(string urlarg)
        {
            Uri uri = this.fixUrl(urlarg);
            string url = uri.ToString();

            this.lastMethodWasPost = false;
            this.url = url;

            bool returnValue = false, isChunked = false;
            string setCookie = string.Empty, location = string.Empty;
            HttpStatusCode statusCode = HttpStatusCode.BadRequest;

            //First, build the URL from the get variables
            HttpWebResponse response = null;
            try
            {
                response = HttpHelper.GetHttpResponse("GET", url, lastSuccessfulUrl, input, cookies, _credentials, timeoutSecs);

                this.isAttachedFile = false;
                this.fileName = string.Empty;
                statusCode = response.StatusCode;
                location = response.Headers["Location"];
                contentLength = response.ContentLength;
                isChunked = (response.Headers["Transfer-Encoding"] == "chunked");
                if (!string.IsNullOrEmpty(response.Headers["Content-Disposition"]))
                {
                    string[] disp = response.Headers["Content-Disposition"].Split(';');
                    foreach (string pair in disp)
                    {
                        int idx = pair.IndexOf("\"");
                        if (idx > 0)
                        {
                            string key = pair.Substring(0, idx-1).Trim().ToLower();
                            string val = pair.Substring(idx+1).Trim('\"');
                            if (key == "filename")
                            {
                                this.isAttachedFile = true;
                                this.fileName = val;
                            }
                        }
                    }
                }

                if (cookies == null || cookies.Count <= response.Cookies.Count)
                {
                    cookies = response.Cookies;
                }

                if (location == null)
                {
                    if ((RedirectOutputStream != null) && RedirectOutputStream.CanWrite)
                    {
                        int length = 0;
                        if (response.ContentEncoding == "gzip")
                        {
                            using (System.IO.Compression.GZipStream zipStream = new System.IO.Compression.GZipStream(response.GetResponseStream(), System.IO.Compression.CompressionMode.Decompress))
                            {
                                length = HttpHelper.StreamToStream(zipStream, RedirectOutputStream);
                            }
                        }
                        else
                        {
                            length = HttpHelper.StreamToStream(response.GetResponseStream(), RedirectOutputStream);
                        }
                    }
                    else
                    {
                        // should be ok to read content
                        if ((response.ContentType.IndexOf("octet-stream") >= 0) ||
                            (response.ContentType.IndexOf("audio") >= 0) ||
                            (response.ContentType.IndexOf("video") >= 0) ||
                            (response.ContentType.IndexOf("zip") >= 0) ||
                            (response.ContentType.IndexOf("image") >= 0))
                        {
                            if (response.ContentEncoding == "gzip")
                            {
                                using (System.IO.Compression.GZipStream zipStream = new System.IO.Compression.GZipStream(response.GetResponseStream(), System.IO.Compression.CompressionMode.Decompress))
                                {
                                    using (System.IO.BinaryReader reader = new System.IO.BinaryReader(zipStream))
                                    {
                                        this.binaryContent = reader.ReadBytes(Convert.ToInt32(response.ContentLength));
                                        this.content = string.Empty;
                                    }
                                }
                            }
                            else
                            {
                                using (System.IO.BinaryReader reader = new System.IO.BinaryReader(response.GetResponseStream()))
                                {
                                    if (isChunked || response.ContentLength <= 0)
                                    {
                                        List<byte> bytes = new List<byte>();
                                        bool keepGoing = true;
                                        while (keepGoing)
                                        {
                                            byte bite = 0;
                                            try
                                            {
                                                bite = reader.ReadByte();
                                                bytes.Add(bite);
                                            }
                                            catch { keepGoing = false; }
                                        }
                                        this.binaryContent = bytes.ToArray();
                                        this.content = string.Empty;
                                    }
                                    else
                                    {
                                        this.binaryContent = reader.ReadBytes(Convert.ToInt32(response.ContentLength));
                                        this.content = string.Empty;
                                    }
                                }
                            }
                        }
                        else
                        {
                            if (response.ContentEncoding == "gzip")
                            {
                                using (System.IO.Compression.GZipStream zipStream = new System.IO.Compression.GZipStream(response.GetResponseStream(), System.IO.Compression.CompressionMode.Decompress))
                                {
                                    using (System.IO.StreamReader reader = new System.IO.StreamReader(zipStream))
                                    {
                                        this.content = reader.ReadToEnd();
                                        this.binaryContent = new byte[] { };
                                    }
                                }
                            }
                            else
                            {
                                using (System.IO.StreamReader reader = new System.IO.StreamReader(response.GetResponseStream()))
                                {
                                    this.content = reader.ReadToEnd();
                                    this.binaryContent = new byte[] { };
                                }
                            }
                        }
                    }

                    returnValue = true;

                    response.Close();
                    response = null;
                }
                else
                {
                    response.Close();
                    response = null;

                    if (redirectCount < maxRedirectCount)
                    {
                        this.input.Clear();
                        // need to forward onto next page
                        returnValue = this.httpGet(location);
                        redirectCount++;
                    }
                }

                this.url = url;
                this.lastSuccessfulUrl = url;
                this.lastWasError = false;
            }
            catch (WebException wex)
            {
                this.lastExceptionStatus = wex.Status;
                this.lastExceptionMessage = wex.Message;
                this.lastExceptionStackTrace = wex.StackTrace;
                this.lastWasError = true;

                this.log.WriteLine("Error Getting [{0}]", url);
                this.log.WriteLine(wex);
                // need to store exception info in browser!
                returnValue = false;
            }

            return returnValue;
        }

        private bool httpPost(string urlarg)
        {
            Uri uri = this.fixUrl(urlarg);
            string url = uri.ToString();

            this.lastMethodWasPost = true;
            this.url = url;

            bool returnValue = false;
            string setCookie = string.Empty, location = string.Empty;
            HttpStatusCode statusCode = HttpStatusCode.BadRequest;

            //First, build the URL from the get variables
            HttpWebResponse response = null;
            try
            {
                response = HttpHelper.GetHttpResponse("POST", url, lastSuccessfulUrl, input, cookies, _credentials, timeoutSecs);

                statusCode = response.StatusCode;
                location = response.Headers["Location"];
                contentLength = response.ContentLength;

                    if (cookies == null || cookies.Count <= response.Cookies.Count)
                    {
                        cookies = response.Cookies;
                    }

                if (location == null)
                {
                    if ((RedirectOutputStream != null) && RedirectOutputStream.CanWrite)
                    {
                        int length = 0;
                        if (response.ContentEncoding == "gzip")
                        {
                            using (System.IO.Compression.GZipStream zipStream = new System.IO.Compression.GZipStream(response.GetResponseStream(), System.IO.Compression.CompressionMode.Decompress))
                            {
                                length = HttpHelper.StreamToStream(zipStream, RedirectOutputStream);
                            }
                        }
                        else
                        {
                            length = HttpHelper.StreamToStream(response.GetResponseStream(), RedirectOutputStream);
                        }
                    }
                    else
                    {
                        if ((response.ContentType.IndexOf("octet-stream") >= 0) ||
                            (response.ContentType.IndexOf("audio") >= 0) ||
                            (response.ContentType.IndexOf("video") >= 0))
                        {

                            Stream s = response.GetResponseStream();
                            System.IO.BinaryReader reader = new System.IO.BinaryReader(s);
                            if (response.ContentLength > 0)
                            {
                                this.binaryContent = reader.ReadBytes(Convert.ToInt32(response.ContentLength));
                            }
                            else
                            {
                                List<byte> bs = new List<byte>();
                                try
                                {
                                    byte b = reader.ReadByte();
                                    while (b >= 0)
                                    {
                                        bs.Add(b);

                                        b = reader.ReadByte();
                                    }
                                }
                                catch (EndOfStreamException)
                                {
                                    // do nothing
                                }
                                this.binaryContent = bs.ToArray();
                            }

                            this.content = string.Empty;
                            reader.Close();
                        }
                        else
                        {
                            System.IO.StreamReader reader = new System.IO.StreamReader(response.GetResponseStream());
                            this.content = reader.ReadToEnd();
                            this.binaryContent = new byte[] { };
                            reader.Close();
                        }
                    }

                    response.Close();
                    response = null;

                    returnValue = true;
                }
                else
                {
                    response.Close();
                    response = null;

                    if (redirectCount < maxRedirectCount)
                    {
                        //this.input.Clear();
                        // need to forward onto next page
                        returnValue = this.httpPost(location);
                        redirectCount++;
                    }
                }

                this.lastSuccessfulUrl = url;
                this.lastWasError = false;
            }
            catch (WebException wex)
            {
                this.lastExceptionStatus = wex.Status;
                this.lastExceptionMessage = wex.Message;
                this.lastExceptionStackTrace = wex.StackTrace;
                this.lastWasError = true;

                this.log.WriteLine("Error Posting [{0}]", url);
                this.log.WriteLine(wex);
                // need to store exception info in browser!
                returnValue = false;
            }

            return returnValue;
        }

        private void clearInput()
        {
            this.backupInput.Clear();
            foreach (string key in this.input.Keys) this.backupInput[key] = input[key];
            this.input.Clear();
        }

        private void restoreInput()
        {
            foreach (string key in this.backupInput.Keys)
            {
                if (!this.input.ContainsKey(key)) input[key] = backupInput[key];
            }
        }
    }
}
